package com.example.dsl

import org.junit.Test
import java.io.File
import java.lang.StringBuilder


/**
 * 采用DSL手写HTML解析器
 * 面向对象规则：依赖导致原则 面向抽象而不是面向细节
 *
 * @author zhouronghua
 * @time 2022/3/26 7:44 下午
 */


class DSLTest02 {

    @Test
    fun testDslHtml() {
        val names = listOf("王特", "李勉", "张大大")
        val result =
            html {
                meta(charset = "utf-8") {}
                head {
                    title { +"使用kotlin进行编码" }
                }
                body { // this == body中转站 { h1 p a p }
                    h1 { // this == h1中转站 { 未知 }
                    }
                    p { -"此格式可用作 HTML 的替代标记" }

                    // 具有属性和文本内容的元素
                    a(href = "http://bbs.xiangxueketang.cn/pins/recommended") { -"享学论坛" }

                    // 混合内容
                    p {
                        -"Derry老师来了"
                        b { -"Derry是谁" }
                        -"文本。有关更多信息，请参阅"
                        a(href = "http://www.xiangxueketang.cn/") { -"湖南享学" }
                        -"Derry的项目"
                    }
                    p { -"一些文字" }

                    // 从命令行参数生成的内容
                    p {
                        -"命令行参数是："
                        ul { // this == UL中转站 { li 子标签  }
                            for (name in names)
                                li { -name } // this == LI中转站
                        }
                    }
                }
            }
        println(result)
        // 将Html写入文件缓存
        val file = File("DDD.html")
        file.writeText(result.toString())
    }
}

/**
 * 元素：每个基本元素html、body、head
 *
 */
private interface Element { // 元素顶层接口

    fun run(builder: StringBuilder, indent: String)
}

/**
 * 显示Text元素
 */
private class TextElement(val text: String) : Element {
    override fun run(builder: StringBuilder, indent: String) {
        builder.append("$indent $text\n")
    }
}

/**
 * Title是一个标签
 */
private class Title : TagClass("title")

/**
 * 显示标签 包括<html> <body>
 */
private open class Tag(val tagName: String) : Element {
    val elements = arrayListOf<Element>() // 标签元素表
    val attributes = hashMapOf<String, String>()  // Map集合 每一个元素 Key=属性名  Value=属性对应的值

    override fun run(builder: StringBuilder, indent: String) {
        // 整个Tag解析过程
        // <html>缩进 属性解析, 有属性的话增加属性
        builder.append("$indent<$tagName${getParseAttributes()}>\n")
        // 添加子节点
        elements.forEach {
            // 递归添加子节点
            it.run(builder, "$indent  ")
        }
        // 添加标签结束符，增加换行符
        builder.append("$indent</$tagName>\n")
    }

    /**
     * 添加属性元素
     * 说明: 如果没有属性则返回空字符串，有属性将属性组合成一个字符串
     */
    private fun getParseAttributes(): String {
        // 通过返回属性字符串处理
        val builder = StringBuilder()
        attributes.forEach {
            builder.append(" ${it.key}=\"${it.value}\"")
        }
        return builder.toString()
    }
}

/**
 * HTML中转站
 */
private class HTML : TagClass("html") {
    /**
     * head函数：输入是一个Head中转站
     */
    fun head(action: Head.() -> Unit) {
        // 添加Head中转站
        val newHead = Head()
        newHead.action()
        // 添加到元素中
        elements.add(newHead)
    }

    fun body(action: Body.() -> Unit) {
        // 添加Body标签
        val newBody = Body("body")
        newBody.action()
        // 添加标签到元素表
        elements.add(newBody)
    }

    fun meta(charset: String, action: Meta.() -> Unit) {
        // 添加meta中转站
        val meta = Meta()
        meta.charset = charset
        meta.action()
        elements.add(meta)
    }

    override fun toString(): String { // 触发点1
        val builder = StringBuilder()
        run(builder, "") // 参数一：StringBuilder方便组装html数据，  参数二：indent不要缩进
        return builder.toString()
    }
}

private class LI : Body("li") // LI中转站
private class UL : Body("ul") {
    // ul 调用 一个或很多 li 标签
    fun li(action: LI.() -> Unit) {
        // 构建标签添加到元素表中
        val newLi = LI()
        newLi.action()
        elements.add(newLi)
    }
}

/**
 * Body一层需要处理的标签函数：
 */
private open class Body(_tagName: String) : TagClass(tagName = _tagName) {
    fun h1(action: H1.() -> Unit) {
        // 添加H1中转站
        val h1 = H1()
        h1.action()
        elements.add(h1)
    }

    fun p(action: P.() -> Unit) {
        // 添加P中转站
        val p = P()
        p.action()
        elements.add(p)
    }

    fun a(href: String, action: A.() -> Unit) {
        // 添加A中转站
        val a = A()
        // 保存属性
        a.href = href
        a.action()
        elements.add(a)
    }

    fun b(action: B.() -> Unit) {
        // 添加B中转站
        val b = B()
        b.action()
        elements.add(b)
    }

    fun ul(action: UL.() -> Unit) {
        // 添加P中转站
        val ul = UL()
        ul.action()
        elements.add(ul)
    }
}

private class Head : TagClass("head") {
    fun title(action: Title.() -> Unit) {
        // 添加Title中转站
        val title = Title()
        title.action()
        elements.add(title)
    }
}

private class H1 : Body("h1")
private class H2 : Body("h2")

/**
 * a标签带有属性链接
 */
private class A : Body("a") {
    var href: String
        get() = attributes["href"]!!
        set(value) {
            attributes["href"] = value
        }
}

/**
 * meta标签带有属性链接
 */
private class Meta : Tag("meta") {
    var charset: String
        get() = attributes["charset"]!!
        set(value) {
            attributes["charset"] = value
        }
}

/**
 * 标签P
 */
private class P : Body("p")
private class B : Body("b")

// 解决 +"AA" -"BBB" 运算符重载
private open class TagClass(tagName: String) : Tag(tagName) {
    /**
     * 加号运算符重载
     */
    operator fun String.unaryMinus() {
        elements.add(TextElement(this))
    }

    /**
     * 减号运算符重载
     * 加号和减号有区别吗？
     */
    operator fun String.unaryPlus() {
        elements += TextElement(this)
    }
}

private fun html(html: HTML.() -> Unit): HTML {
    val htmlObj = HTML()
    htmlObj.html()
    return htmlObj
}